package com.ikas.ai.server.module.analyse.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ikas.ai.server.module.analyse.algorithm.QuartileCalculator;
import com.ikas.ai.server.module.analyse.dorisDao.DorisExceptionAnalysisDAO;
import com.ikas.ai.server.module.analyse.model.dto.ExceptionQueryDTO;
import com.ikas.ai.server.module.analyse.model.dto.TrendQueryDTO;
import com.ikas.ai.server.module.analyse.model.vo.ExceptionAnalysisExcelVo;
import com.ikas.ai.server.module.analyse.model.vo.ExceptionAnalysisPageVo;
import com.ikas.ai.server.module.analyse.model.vo.ExceptionAnalysisVo;
import com.ikas.ai.server.module.analyse.model.vo.QuartileVo;
import com.ikas.ai.server.module.instance.model.DorisWorkInstanceStepDetail;
import com.ikas.ai.utils.BeanUtils;
import com.ikas.ai.utils.DateTimeUtil;
import com.ikas.ai.utils.EasyPoiUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 异常趋势分析
 *
 * @author tang.xuandong
 * @version 1.0.0
 * @date 2023/5/4 16:04
 */
@Service
public class DorisExceptionAnalysisService extends ServiceImpl<DorisExceptionAnalysisDAO, DorisWorkInstanceStepDetail> {

    @Autowired
    private DorisExceptionAnalysisDAO dorisExceptionAnalysisDAO;

    @Value("${work-instance.step-detail.cost-time.max-length:100}")
    Integer maxStepDetailCostTimeLength;

    /**
     * 列表 - 图表数据
     *
     * @param req 查询条件
     * @return vo
     */
    public ExceptionAnalysisPageVo chartQueryException(ExceptionQueryDTO req) {
        final ExceptionAnalysisPageVo result = new ExceptionAnalysisPageVo();
        req.setEndTime(DateTimeUtil.plusEndTime(req.getEndTime(), 1));
        final List<ExceptionAnalysisVo> exceptionAnalysis = dorisExceptionAnalysisDAO.getExceptionAnalysis(req);
        final Map<String, QuartileVo> cache = new HashMap<>();
        for (ExceptionAnalysisVo analysis : exceptionAnalysis) {
            analysis.setStartTime(analysis.getStartTimestamp().toString());
            analysis.setEndTime(analysis.getEndTimestamp().toString());
            String meteCode = analysis.getMeteCode();
            QuartileVo quartileVo = cache.get(meteCode);
            if (quartileVo == null) {
                TrendQueryDTO param = new TrendQueryDTO();
                param.setMachineNo(req.getMachineNo());
                param.setWfName(req.getWfName());
                param.setMeteCode(meteCode);
                param.setStartTime(req.getStartTime());
                param.setEndTime(req.getEndTime());
                param.setCount(maxStepDetailCostTimeLength);
                quartileVo = getQuartile(param);    // 四分位
            }
            if (quartileVo != null) {
                cache.put(meteCode, quartileVo);
                // 异常边界
                Double upperLimit = quartileVo.getUpperLimit(); // 上限
                Double lowerLimit = quartileVo.getLowerLimit(); // 下限
                if (upperLimit != null && lowerLimit != null) {
                    if (analysis.getCostTime() > upperLimit || analysis.getCostTime() < lowerLimit) {
                        analysis.setException(true);
                    }
                }
            }
        }
        final Map<String, List<ExceptionAnalysisVo>> listMap
                = exceptionAnalysis.stream().collect(Collectors.groupingBy(ExceptionAnalysisVo::getMeteCode));
        result.setAnalysis(listMap);
        result.setQuartiles(cache);
        return result;
    }

    /**
     * 分页查询
     *
     * @param req req
     * @return list
     */
    public List<ExceptionAnalysisVo> pageQueryException(ExceptionQueryDTO req) {
        req.setEndTime(DateTimeUtil.plusEndTime(req.getEndTime(), 1));
        final List<ExceptionAnalysisVo> exceptionAnalysis = dorisExceptionAnalysisDAO.getExceptionAnalysis(req);
        exceptionAnalysis.forEach(d -> {
            d.setStartTime(d.getStartTimestamp().toString());
            d.setEndTime(d.getEndTimestamp().toString());
        });
        final Map<String, QuartileVo> cache = new HashMap<>();
        for (ExceptionAnalysisVo analysis : exceptionAnalysis) {
            analysis.setStartTime(analysis.getStartTimestamp().toString());
            analysis.setEndTime(analysis.getEndTimestamp().toString());
            String meteCode = analysis.getMeteCode();
            QuartileVo quartileVo = cache.get(meteCode);
            if (quartileVo == null) {
                TrendQueryDTO param = new TrendQueryDTO();
                param.setMachineNo(req.getMachineNo());
                param.setWfName(req.getWfName());
                param.setMeteCode(meteCode);
                param.setStartTime(req.getStartTime());
                param.setEndTime(req.getEndTime());
                param.setCount(maxStepDetailCostTimeLength);
                quartileVo = getQuartile(param);    // 四分位
            }
            if (quartileVo != null) {
                cache.put(meteCode, quartileVo);
                analysis.setQuartileVo(QuartileVo.build(quartileVo));
                // 异常边界
                Double upperLimit = quartileVo.getUpperLimit(); // 上限
                Double lowerLimit = quartileVo.getLowerLimit(); // 下限
                if (upperLimit != null && lowerLimit != null) {
                    if (analysis.getCostTime() > upperLimit || analysis.getCostTime() < lowerLimit) {
                        analysis.setException(true);
                    }
                }
            }
        }
        return exceptionAnalysis;
    }


    /**
     * 获取四分位
     *
     * @param req req
     * @return QuartileVo
     */
    public QuartileVo getQuartile(TrendQueryDTO req) {
        // 采样数据
        // 解析data：q1=data[0] q2=data[1] q3=data[2]
        final List<Double> sampleData = dorisExceptionAnalysisDAO.getSampleData(req);
        double[] data = QuartileCalculator.calculateIQR(sampleData);
        if (data != null && data.length != 0) {
            double iqr = data[2] - data[0];
            final QuartileVo quartileVo = new QuartileVo();
            quartileVo.setIqr(BigDecimal.valueOf(iqr).setScale(3, RoundingMode.HALF_UP).doubleValue());                 // 四分位间距
            quartileVo.setUpperQuartile(BigDecimal.valueOf(data[2]).setScale(3, RoundingMode.HALF_UP).doubleValue());   // 上四分位数（q3）
            quartileVo.setMedian(BigDecimal.valueOf(data[1]).setScale(3, RoundingMode.HALF_UP).doubleValue());          // 中位数（q2）
            quartileVo.setLowerQuartile(BigDecimal.valueOf(data[0]).setScale(3, RoundingMode.HALF_UP).doubleValue());   // 下四分位数（q1）
            quartileVo.setAvg(BigDecimal.valueOf(QuartileCalculator.avg(sampleData)).setScale(3, RoundingMode.HALF_UP).doubleValue());  // 平均值
            quartileVo.setUpperLimit(BigDecimal.valueOf(data[2] + 1.5 * iqr).setScale(3, RoundingMode.HALF_UP).doubleValue());  // 上限（上须）
            quartileVo.setLowerLimit(BigDecimal.valueOf(data[0] - 1.5 * iqr).setScale(3, RoundingMode.HALF_UP).doubleValue());  // 下限（下须）
            return quartileVo;
        }
        return null;
    }


    /**
     * 导出
     *
     * @param response response
     * @param req      dto
     */
    public void export(HttpServletResponse response, ExceptionQueryDTO req) {
        req.setEndTime(DateTimeUtil.plusEndTime(req.getEndTime(), 1));
        final List<ExceptionAnalysisVo> exceptionAnalysis = dorisExceptionAnalysisDAO.getExceptionAnalysis(req);
        exceptionAnalysis.forEach(d -> {
            d.setStartTime(d.getStartTimestamp().toString());
            d.setEndTime(d.getEndTimestamp().toString());
        });
        final List<ExceptionAnalysisExcelVo> list = exceptionAnalysis.stream().map(exceptionAnalysisVo -> {
            ExceptionAnalysisExcelVo excelVo = BeanUtils.copy(exceptionAnalysisVo, ExceptionAnalysisExcelVo.class);
            excelVo.setCostTime(String.valueOf(exceptionAnalysisVo.getCostTime()));
            excelVo.setStartTime(exceptionAnalysisVo.getStartTimestamp().toString());
            excelVo.setEndTime(exceptionAnalysisVo.getEndTimestamp().toString());
            return excelVo;
        }).collect(Collectors.toList());
        final Map<String, QuartileVo> cache = new HashMap<>();
        for (ExceptionAnalysisExcelVo vo : list) {
            String meteCode = vo.getMeteCode();
            QuartileVo quartileVo = cache.get(meteCode);
            if (quartileVo == null) {
                TrendQueryDTO dto = new TrendQueryDTO();
                dto.setMachineNo(req.getMachineNo());
                dto.setWfName(req.getWfName());
                dto.setMeteCode(meteCode);
                dto.setStartTime(req.getStartTime());
                dto.setEndTime(req.getEndTime());
                dto.setCount(maxStepDetailCostTimeLength);
                quartileVo = getQuartile(dto);
            }
            if (quartileVo != null) {
                cache.put(meteCode, quartileVo);
                QuartileVo build = QuartileVo.build(quartileVo);
                vo.setUpperLimit(Objects.nonNull(build.getUpperLimit()) ? String.valueOf(build.getUpperLimit()) : "-");
                vo.setUpperQuartile(Objects.nonNull(build.getUpperQuartile()) ? String.valueOf(build.getUpperQuartile()) : "-");
                vo.setAvg(Objects.nonNull(build.getAvg()) ? String.valueOf(build.getAvg()) : "-");
                vo.setMedian(Objects.nonNull(build.getMedian()) ? String.valueOf(build.getMedian()) : "-");
                vo.setLowerQuartile(Objects.nonNull(build.getLowerQuartile()) ? String.valueOf(build.getLowerQuartile()) : "-");
                vo.setLowerLimit(Objects.nonNull(build.getLowerLimit()) ? String.valueOf(build.getLowerLimit()) : "-");
            }
        }
        // Export
        EasyPoiUtil.exportExcel(list, null, "执行响应异常分析", ExceptionAnalysisExcelVo.class,
                "ExceptionAnalysis.xlsx", true, response);
    }

}
